
"""
 * Copyright 2009 Yogi KENT
 * 
 * This file is main part of Sotetsu.
 * 
 * Sotetsu is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 * 
 * Sotetsu is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.

"""

import curses
import curses.panel
import logging
import datetime
import threading

#from sotetsu_pre import *
from .._sotetsu_utils import *
from .._sotetsu import *
from ..daap import daapclient
from ..player import simpleplayer
from .. import wcwidth


class itemListPanel:
	"""Manages terminal window.

	This class manages terminal window which has "height"
	rows and "width" columns.  Because we cannot use insstr,
	we have to use addstr function.  so right most character
	must not be printed to avoid auto scrolling.
	"""
	def __init__(self, height, width, colorattr):
		# it should be tested in caller.
		assert width >= 60  , "This window is too small!!"
		assert height >= 16 , "This window is too small!!"

		# create window and panel
		self.win = curses.newwin(height,width,0,0)
		self.win.keypad(True)
		self.win.setscrreg(1, height-1)
		self.win.scrollok(True)
		self.panel = curses.panel.new_panel(self.win)

		# initialize members
		self.toppos = 1         # first printable line
		self.lines = height-1   # number of lines where it can print item's information on
		self.width = width-1    # number of characters that can be printed on one line.
		                        # it's not so good solution, read TODO file.
		self.currentpos = 0     # current position of selecting item (scrlpos <= currentpos <= scrlpos+height-1 )
		self.scrlpos = 0        # current scrolling offset (0 <= scrlpos <= len(itemList) - height
		# 
		self.num_col    = 5
		self.artist_col = 20    # they are modified later.
		self.album_col 	= 20
		self.title_col 	= self.width - self.artist_col - self.album_col - self.num_col - 3

		self.ca = colorattr

	def createFiltered(self, filter):
		refinedItems = []
		for item in self.itemList:
			if filter(item):
				refinedItems.append(item)
		yx = self.win.getmaxyx()
		ilp = itemListPanel(yx[0], yx[1], self.ca)
		ilp.setItems(refinedItems)
		return ilp

	def resize(self, height, width):
		(y,x) = self.win.getmaxyx()
		if y==height and x==width:
			refreshEntireWindow()

		old = self.win
		self.win = curses.newwin(height,width,0,0)
		self.win.keypad(True)
		self.panel.replace(self.win)

		self.toppos = 1			# first printable line
		self.lines = height-1	# number of lines where it can print item info on
		self.width = width-1      # number of characters that can be printed on one line.
		self.win.setscrreg(1, height-1)
		self.win.scrollok(True)
		self.num_col    = 5
		self.artist_col = 20
		self.album_col 	= 20
		self.title_col 	= width - self.artist_col - self.album_col - self.num_col - 3
		if self.currentpos - self.scrlpos >= self.lines:
			self.currentpos = self.scrlpos + self.lines - 1

		self.refreshEntireWindow()
	def hide(self):
		self.panel.hide()
	def show(self):
		self.panel.show()

	def getmaxyx(self):
		return self.win.getmaxyx()

	def setItems(self, items):
		self.itemList = items
	def getItems(self):
		return self.itemList

	def setStatusLineManager(self, sl):
		self.slManager = sl

	def refreshEntireWindow(self):
		self.__printHeader()
		for i in range(self.lines):
			if not i<len(self.itemList):
				break
			self.__print1Line(i)
		#self.slManager.printStatusLine()
		#self.win.refresh()
		curses.panel.update_panels()
		curses.doupdate()

	def __printLines(self, r):
		"""prints len(r) lines."""
		for i in r:
			self.__print1Line(i)

	def __print1Line(self, n):
		"""prints 1 line pointed by n."""
		assert 0 <= n < self.lines
		item = self.itemList[n+self.scrlpos]
		if n+self.scrlpos==self.currentpos:
			attr = self.ca.getAttr("cursor")
		else:
			attr = self.ca.getAttr("normal")
		self.win.addstr(n+1, 0, self.__formatItem(n+self.scrlpos, item, self.width), attr)

	def __printHeader(self):
		numstr= widthcenter("num", self.num_col, "─")
		artist= widthcenter(DISPTAG[daapclient.SONGARTIST], self.artist_col, "─")
		album = widthcenter(DISPTAG[daapclient.SONGALBUM], self.album_col, "─")
		title = widthcenter(DISPTAG[daapclient.ITEMNAME], self.title_col, "─")
		#txt = artist +"|"+ album +"|"+ title
		txt = "┬".join( (numstr, artist, album, title) )
		#txt = curses.ACS_VLINE.join( (artist, album, title) )
		self.win.addstr(0, 0, txt)

	def __formatItem(self, num, item, column):
		"""prints item informations:artist,album,title.

		it also can allow users to determine howto format item information.
		which is prefer?
		"""
		numstr= rjust(str(num), self.num_col)
		artist= widthpad(item[daapclient.SONGARTIST], self.artist_col)
		album = widthpad(item[daapclient.SONGALBUM], self.album_col)
		title = widthpad(item[daapclient.ITEMNAME], self.title_col)
		#txt = artist +"|"+ album +"|"+ title
		txt = "│".join( (numstr, artist, album, title) )
		#txt = chr(curses.ACS_VLINE).join( (artist, album, title) )
		return txt


	def getCurrentItem(self):
		return self.itemList[self.currentpos]
	def getCurrentPosition(self):
		return self.currentpos

	def searchItem(self, str):
		cond = { }
		cond[daapclient.ITEMNAME]   = re.compile(str)
		cond[daapclient.SONGARTIST] = re.compile(str)
		cond[daapclient.SONGALBUM]  = re.compile(str)
		for i in range(self.currentpos, len(self.itemList)):
			if matchRegexOR(self.itemList[i], cond):
				self.currentpos = i
				self.scrlpos = i - self.lines//2
				if self.scrlpos < 0:
					self.scrlpos=0
				elif self.scrlpos > len(self.itemList) - self.lines:
					self.scrlpos = len(self.itemList)-self.lines

				self.refreshEntireWindow()
				return True

		return False
	def nextItem(self):
		"""forward the cursor to point next item."""
		if self.currentpos==len(self.itemList)-1: return False
		if self.currentpos==self.scrlpos+self.lines-1:
			if not self.__scroll(1): return False
		self.currentpos += 1
		i = self.currentpos - self.scrlpos
		self.__printLines( (i-1, i) )
		#self.__printHeader()
		curses.panel.update_panels()
		curses.doupdate()
		# 本当は下ので実現したいんだけど、ワイド文字に対応してない?
		#self.win.chgat(self.currentpos - self.scrlpos ,0, curses.A_NORMAL)
		#self.currentpos += 1
		#self.win.chgat(self.currentpos - self.scrlpos ,0, curses.A_REVERSE)
		#self.win.touchline(self.currentpos-self.scrlpos, 2, 1)
		return True
	def prevItem(self):
		"""backward the cursor to point previous item."""
		if self.currentpos==self.scrlpos:
			if not self.__scroll(-1): return False
		self.currentpos -= 1
		i = self.currentpos - self.scrlpos
		self.__printLines( (i, i+1) )
		#self.win.refresh()
		curses.panel.update_panels()
		curses.doupdate()
		return True


	def __scroll(self, num):
		"""scroll this window"""
		if num==0: return False
		elif not 0 <= self.scrlpos+num <= len(self.itemList)-self.lines:
			return False

		self.win.scroll(num)
		self.scrlpos += num

		#if not self.scrlpos <= self.currentpos < self.scrlpos+self.lines:
		if not self.scrlpos <= self.currentpos:
			self.currentpos = self.scrlpos
			self.__print1Line(0)
		elif not self.currentpos < self.scrlpos+self.lines:
			self.currentpos = self.scrlpos + self.lines-1
			self.__print1Line(self.lines-1)
		
		#num>0:  height-num ==> height
		#num<0:  0 ==> num
		# TODO:  in the case of  abs(num) > height
		if num>0: r = range(self.lines-num, self.lines)
		elif num<0: r = range(0, -num)
		self.__printLines(r)

		return True

	def __iter__(self):
		"""for getch iterator"""
		return self
	def __next__(self):
		"""for getch iterator"""
		return self.win.getch()

	def scroll(self, num):
		bool = self.__scroll(num)
		#self.win.refresh()
		self.__printHeader()
		curses.panel.update_panels()
		curses.doupdate()
		return bool
	def scrollDownHalf(self):
		return self.scroll( self.lines//2 )
	def scrollUpHalf(self):
		return self.scroll( -self.lines//2 )

class tagInputPanel:
	# it requires more refactoring.
	"""this class displays TAG information input window.

	This class allows user to input TAG information.
	and now, inputted data is used for refining itemList to
	be passed to refineItemList function.
	TODO: now, we cannot input without calling curses.endwin
	function. getstr cannot be used for multibyte strings.
	"""
	def __init__(self, max):
		#self.items = items
		self.items = ( daapclient.ITEMNAME, daapclient.SONGALBUM, daapclient.SONGARTIST )
		self.data  = { tag: None for tag in self.items  }
		self.OKorCANCEL = 0 # 0: tag, 1: CANCEL, 2:OK

		self.y_offset = 2
		self.x_offset = 2

		# compute required width and height.
		self.width  = 4 + TAG_W + self.__maxDataWidth()
		self.height = 4 + len(self.items)

		# make window and panel
		self.win = curses.newwin(self.height, self.width, curses.LINES-self.height, curses.COLS-self.width-1)
		self.win.keypad(True)
		self.panel = curses.panel.new_panel(self.win)
		self.win.box()
		self.panel.show()

		# draw the window.
		self.currentPos = 0
		self.__printItems()

	def mainLoop(self):
		logging.debug("start tagInputPanelLoop routine.")
		while True:
			ch = self.win.getch()
			logging.debug("key {0:d}({1:s}) pressed".format(ch, curses.keyname(ch)))
			if ch==ord('q') or ch==curses.KEY_F1:
				stop_player()
				break
			elif ch==ord('j') or ch==curses.KEY_DOWN:
				logging.debug("press 'j'")
				rtn = self.nextTag()
			elif ch==ord('k') or ch==curses.KEY_UP:
				logging.debug("press 'k'")
				rtn = self.prevTag()
			elif ch==curses.KEY_ENTER or ch==ord('i'):
				s = self.getState()
				if s==0:
					self.inputTagInfo()
				elif s==1:
					return None
				elif s==2:
					return self.getTagInfo()
				rtn = True
			else:
				logging.debug("Unknown key {0:d}({1!s:s}) was inputted".format(ch, curses.keyname(ch)))
				rtn = False

			curses.panel.update_panels()
			curses.doupdate()
			if not rtn:
				curses.beep()
		logging.debug("end tagInputPanelLoop routine.")

	def __maxDataWidth(self):
		width = 10
		for value in self.data.values():
			if value==None: continue
			w = wcwidth.wcswidth(value)
			if w > width: width = w
		return width

	def setData(key, value):
		self.data[key] = value
	def remakeWindow(self):
		data_w = self.__maxDataWidth()
		self.width  = TAG_W + data_w + 4
		self.height = len(self.items) + 4
		old = self.win
		self.win = curses.newwin(len(self.items)+4, self.width, 1, curses.COLS-self.width-1)
		self.win.keypad(True)
		self.win.box()
		self.panel.replace(self.win)
		self.__printItems()
	def __printItems(self):
		"""displays entire window."""
		# starting position.
		y = self.y_offset
		x = self.x_offset

		# use tempolary data for None.
		tmpdata = dict(self.data) 
		for (key,value) in tmpdata.items():
			if value==None:
				tmpdata[key] = " "*10

		# print each items
		for item in self.items:
			# prepare data which is printed.
			tag = rjust(DISPTAG[item]+TAGSEP, TAG_W)
			str = widthcut(tmpdata[item], self.width-TAG_W-2*self.x_offset)

			# print a tag and string corresponding it.
			self.win.move(y, x)
			self.win.addstr(tag)
			self.win.addstr(str, curses.A_UNDERLINE)
			y += 1

		# refresh
		self.__drawCursor()
		curses.panel.update_panels()
		curses.doupdate()

	def __drawCursor(self):
		self.__printOKCANCEL()
		if 0!=self.OKorCANCEL:
			curses.curs_set(0)
		else:
			curses.curs_set(1)
			y = self.y_offset + self.currentPos
			x = self.y_offset + TAG_W
			self.win.move(y, x)
	def __printOKCANCEL(self):
		OK = "[ {0:s} ]".format(OK_LABEL)
		CANCEL = "[ {0:s} ]".format(CANCEL_LABEL)
		OK_w = wcwidth.wcswidth(OK)
		CANCEL_w = wcwidth.wcswidth(CANCEL)
		y = self.y_offset + len(self.items)
		x = self.width - OK_w - CANCEL_w - 3
		if self.OKorCANCEL==1:
			self.win.addstr(y, x, OK, curses.A_NORMAL)
			self.win.addstr(y, x+OK_w+1, CANCEL, curses.A_REVERSE)
		elif self.OKorCANCEL==2:
			self.win.addstr(y, x, OK, curses.A_REVERSE)
			self.win.addstr(y, x+OK_w+1, CANCEL, curses.A_NORMAL)
		else:
			self.win.addstr(y, x, OK, curses.A_NORMAL)
			self.win.addstr(y, x+OK_w+1, CANCEL, curses.A_NORMAL)


	def nextTag(self):
		if self.currentPos < len(self.items) -1:
			self.currentPos += 1
		elif self.OKorCANCEL==0:
			self.OKorCANCEL=1
		elif self.OKorCANCEL==1:
			self.OKorCANCEL=2
		self.__drawCursor()
		return True
	def prevTag(self):
		if self.OKorCANCEL==2:
			self.OKorCANCEL=1
		elif self.OKorCANCEL==1:
			self.OKorCANCEL=0
			self.currentPos=len(self.items)-1
		elif self.currentPos > 0:
			self.currentPos -= 1
		self.__drawCursor()
		return True
	def inputTagInfo(self):
		if self.OKorCANCEL==0:
			pos = self.currentPos
			#curses.def_prog_mode()
			#curses.endwin()
			prompt = self.items[pos].decode()+" >"
			# can I use readline without endwin()?
			#str = input(prompt)
			#self.win.refresh()
			str = getString(self.win, prompt)
			self.data[self.items[pos]] = str
			self.remakeWindow()
			return True
		else:
			return False
	def getState(self):
		return self.OKorCANCEL
	def getTagInfo(self):
		return self.data


		


class infoPanel:
	"""information panel.

	This class displays music information on top-left of window.
	it displays artist, title and album now.  you need any more?
	"""
	# TODO: refactoring
	def __init__(self, max, tags):
		self.max_width = max
		self.win = curses.newwin(5, self.max_width, 1, curses.COLS-self.max_width-1)
		self.panel = curses.panel.new_panel(self.win)
		self.panel.hide()
		self.isshowed = False
		self.tags = tags

	def show(self):
		self.panel.show()
		self.isshowed = True
		curses.panel.update_panels()
		curses.doupdate()
	def hide(self):
		self.panel.hide()
		self.isshowed = False
		curses.panel.update_panels()
		curses.doupdate()

	def setItem(self, item):
		width = self.max_width - TAG_W - 2  # represents max tag info length.
		                                    # 2 is for border.
		tagstr = { }    # has splited string which is in item.
		tagstr_w = 0    # max length of string tagstr has.
		win_height = 0  # window height (sum of len(tagstr[i][0])+3).

		# split tag info and compute length of them.
		for tag in self.tags:
			tagstr[tag] = widthsplit(item[tag], width)
			w = wcwidth.wcswidth(tagstr[tag][0])
			if tagstr_w < w: tagstr_w = w
			win_height += len(tagstr[tag])

		# create window and panel.
		win_width = TAG_W+tagstr_w+2
		win_height += 3
		old = self.panel.window()
		win = curses.newwin(win_height,win_width, 1, curses.COLS - win_width -1)
		win.box()
		self.panel.replace(win)

		# draw informations.
		i = 2
		for tag in self.tags:
			win.addstr(i, 1, rjust(DISPTAG[tag]+TAGSEP,TAG_W))
			for str in tagstr[tag]:
				win.addstr(i, TAG_W+1, str)
				i += 1

		curses.panel.update_panels()
		curses.doupdate()

class infoPanelwithTime(infoPanel):
	def __init__(self, max, tags):
		infoPanel.__init__(self, max, tags)
		self._sec = -1

	def setItem(self, item):
		self.item = item
		width = self.max_width - TAG_W - 2  # represents max tag info length.
		                                    # 2 is for border.
		tagstr = { }    # has splited string which is in item.
		tagstr_w = 0    # max length of string tagstr has.
		self.win_height = 0  # window height (sum of len(tagstr[i][0])+3).

		# split tag info and compute length of them.
		for tag in self.tags:
			tagstr[tag] = widthsplit(item[tag], width)
			w = wcwidth.wcswidth(tagstr[tag][0])
			if tagstr_w < w: tagstr_w = w
			self.win_height += len(tagstr[tag])

		# create window and panel.
		self.win_width = TAG_W+tagstr_w+2
		self.win_height += 4
		old = self.panel.window()
		self.win = curses.newwin(self.win_height,self.win_width, 1, curses.COLS - self.win_width -1)
		self.win.box()
		self.panel.replace(self.win)

		# draw informations.
		i = 2
		for tag in self.tags:
			self.win.addstr(i, 1, rjust(DISPTAG[tag]+TAGSEP,TAG_W))
			for str in tagstr[tag]:
				self.win.addstr(i, TAG_W+1, str)
				i += 1

		self.starttime = datetime.datetime.now()
		self.delta = datetime.timedelta()
		self.printTime()

		#self.timer = threading.Timer(5.0 , lambda :self.__printTime())
		#self.timer.start()
	
	def printTime(self):
		### print "{0:02d}:{1:02d} / {2:02d}:{3:02d}".format(min,sec, tmin,tsec)
		self.__printTotalTime()
		self.updateTime(datetime.timedelta(0))

	def __printTotalTime(self):
		total = self.item[daapclient.SONGTIME]
		(min, sec) = divmod(total//1000, 60)
		txt = " / {0:02d}:{1:02d}".format(min, sec)
		pos = self.win_width//2
		self.win.addstr(self.win_height-2, pos, txt)

	def updateTime(self, time):
		# for supress CPU rate
		if time.seconds==self._sec: return
		else: self._sec=time.seconds

		(min, sec) = divmod(time.seconds, 60)

		txt = "{0:02d}:{1:02d}".format(min, sec)
		pos = self.win_width//2 - len(txt)
		self.win.addstr(self.win_height-2, pos, txt)

		curses.panel.update_panels()
		curses.doupdate()


class messagePanel:
	def __init__(self, y, x, height, width, colorattr):
		# initialize members
		self.height = height    # number of lines where it can print item's information on
		self.width = width-1    # number of characters that can be printed on one line.

		# create window and panel
		self.win = curses.newwin(height,width,y,x)
		self.win.keypad(True)
		self.panel = curses.panel.new_panel(self.win)

		#self.msgAttr = msgAttr
		#self.errAttr = errAttr
		self.ca = colorattr

	def clearMessage(self):
		self.win.clear()
	def putMessage(self, msg):
		# TODO:  assert 
		text = widthcut(msg, self.width)
		self.win.addstr(0, 0, text, self.ca.getAttr("message"))
	def putErrorMessage(self, msg):
		text = widthcut(msg, self.width)
		self.win.addstr(0, 0, text, self.ca.getAttr("error"))
	def askYesNo(self, msg):
		self.putMessage(msg+" ([Yes]/No): ")

		curses.nocbreak()
		curses.echo()
		curses.curs_set(1)

		rtn = self.win.getstr().decode().lstrip()
		while len(rtn)<=0:
			curses.beep()
			self.putMessage(msg+" ([Yes]/No): ")
			rtn = self.win.getstr().decode().lstrip()
		logging.debug("messagePanel gets ``{0!s:s}''".format(rtn))

		curses.cbreak()
		curses.noecho()
		curses.curs_set(1)

		self.clearMessage()
		if rtn[0]=='Y' or rtn[0]=='y':
			return True
		else:
			return False

	def getString(self, msg):
		rtn = getString(self.win, msg)
		self.clearMessage()
		return rtn



class statusLinePanel:
	def __init__(self):
		self.width = curses.COLS
		self.win = curses.newwin(1,self.width,curses.LINES-1,0)
		self.win.keypad(True)
		self.win.scrollok(False)
		self.panel = curses.panel.new_panel(self.win)
		self.panel.top()
	def printMessage(self, str):
		str = widthcut(str, self.width-1)
		self.win.addstr(0,0, str)
		curses.panel.update_panels()
		curses.doupdate()

